(function(){
	translator.core = function(){
		this.isOnTextArea = false;
		this._textArea = null;
		this._lastTextArea = null;
		this._init();
	};
	/*
		Function: attachEvent
		
		Attach event to the native element.
		
		Parameters:
			oElement - The element to be attached.
			sEventName - The event name. (keydown, keyup, click....)
			pFunc - The function to attach.
	*/
	translator.core.attachEvent = function(oElement, sEventName, pFunc)
	{
		if (oElement.attachEvent){
			oElement.attachEvent("on" + sEventName.toLowerCase(), pFunc);
		}
		else if (oElement.addEventListener){
			oElement.addEventListener(sEventName.toLowerCase(), pFunc, false);
		}
		else{
			if (oElement["on"+sEventName] == null){
				oElement["on"+sEventName] = mapo.event();
			}
			if (oElement["on"+sEventName].hook != null){
				oElement["on"+sEventName].hook(pFunc);
			}
		}
	};

	/*
		Function: detachEvent
		
		Detach event from the native element.
		
		Parameters:
			oElement - The element from be detached.
			sEventName - The event name. (keydown, keyup, click....)
			pFunc - The function to detach.
	*/
	translator.core.detachEvent = function(oElement, sEventName, pFunc)
	{
		if (oElement.detachEvent){
			oElement.detachEvent("on" + sEventName.toLowerCase(), pFunc);
		}
		else if (oElement.removeEventListener){
			oElement.removeEventListener(sEventName.toLowerCase(), pFunc, false);
		}
		else{
			if (oElement["on"+sEventName] == null){
				oElement["on"+sEventName] = mapo.event();
			}
			if (oElement["on"+sEventName].unhook != null){
				oElement["on"+sEventName].unhook(pFunc);
			}
		}
	};
	var p = translator.core.prototype;
	p._init = function(){
		textareas = document.getElementsByTagName("textarea");
		var i = textareas.length;
		while(i--){
			// hook event to all textarea
			translator.core.attachEvent(textareas[i], "focus", mapo.me(this, this._textarea_Focus));
			translator.core.attachEvent(textareas[i], "blur", mapo.me(this, this._textarea_Blur));
		}
	};
	
	p._textarea_Focus = function(e){
		if (e == null){
			e = event;
		}
		var target = e.target || e.srcElement;
		
		if (target.tagName.toUpperCase() == "TEXTAREA"){
			this.isOnTextArea =true;
			this._textArea = target;
		}
	};
	p._textarea_Blur = function(e){
		if (e == null){
			e = event;
		}
		var target = e.target || e.srcElement;
		if (target == this._textArea){
			this.isOnTextArea =false;
		}
		if (target.tagName.toUpperCase() == "TEXTAREA"){
		    this._lastTextArea = target;
		}
	};
	
	/*
	    Function: getSelection
	    get current selection on page x-browserly
	*/
	p.getSelection = function(){
		var ret = null;
		if (window.getSelection) ret = window.getSelection();
		else if (document.getSelection) ret = document.getSelection();
		else if (document.selection) ret = document.selection;
		return translator.selection.wrap(ret);
	};
	
	/*
	    Function: getSelectedText
	    return highlighted text
	*/
	p.getSelectedText = function(){
	    var ret = this.getSelection().toString();
	    // for browsers that will lose its focus when clicking on button.
	    if (ret == String.empty && this._lastTextArea != null){
	        this._lastTextArea.focus();
	        ret = this.getSelection().toString();
	    }
		return ret;
	};
	
	/*
	    Function: setSelectedText
	    Replace highlighted text with specified message
	*/
	p.setSelectedText = function(msg){
		var range  = this.getSelection().getRangeAt();
		var selection = this.getSelection();
		
		var elEditable = this.seekUnremovable(range);
		if (elEditable != null){
			if (elEditable.selectionStart != null){
				var before = elEditable.value.substr(0, elEditable.selectionStart);
				var after = elEditable.value.substr(elEditable.selectionEnd, elEditable.value.length - elEditable.selectionEnd);
				elEditable.value = before + msg + after;
			}
			// ie
			else{
				range.text(msg);
			}
		}
		else{
			range.deleteContents();
			var wrapper = document.createElement("div");
			wrapper.innerHTML = msg.replace(/\n/g, "<br />");
			var i = wrapper.childNodes.length;
			while(i--> 0){
				range.insertNode(wrapper.childNodes[i]);
			}
		}
		//range.detach();
	};
	
	/*
	    Function: seekUnremovable
	    
	    Seeking for elements that its content cannot be cleared by calling range.deleteContents
	*/
	p.seekUnremovable = function(range){
		var ret = null;
		var parent = range.commonAncestorContainer();
		
		if (parent == null){
			return ret;
		}
		
		if (parent.tagName != null &&
			parent.tagName.toUpperCase() == "TEXTAREA"){
			ret = parent;
		}
		else if (parent.childNodes.length == 1 && 
			parent.childNodes[0].tagName != null &&
			parent.childNodes[0].tagName.toUpperCase() == "TEXTAREA"){
			ret = parent.childNodes[0];
		}
		else if (this.isOnTextArea){
			ret = this._textArea;
		}
		else {
			var textareas = document.getElementsByTagName("textarea");
			var i = textareas.length;
			while(i--){
				var t=textareas[i];
				if (t.selectionStart != null && 
					t.selectionStart != t.selectionEnd){
					ret = t;
					break;
				}
			}
		}
		return ret;
	};
	/*
	    Function: translate
	    
	    If highlighted texts are encoded, decode they. otherwise, encode they.
	*/
	p.translate = function(msg, encoding){
		var encoders = translator.encodings.availables;
		
		// decoding
		var i = encoders.length;
		while(i--){
			if (encoders[i].isEncoded(msg)){
				return encoders[i].decode(msg);
			}
		}
		// runs to here means texts were not decoded, 
		// so let's start encoding
		if (encoding == null){
			// nx(20100101): TODO random encoder
			return "";
		}
		
		i = encoders.length;
		while(i--){
			if (encoders[i]._core.wrapper == encoding){
				return encoders[i].encode(msg);
			}
		}
		
		return "";
	};
	
	/*
	    Function: create
	    
	    Create an instance of translator.
	*/
	translator.create = function(){
		if (translator.i == null){
			translator.i = new translator.core();
		}
		return translator.i;
	};
})();